﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

/**
 * An Environment used for creating and editing levels.
 * Will load a given level, then let the user place objects and
 * save that work to a file for the particular level.
 */
public class EditorEnv : EnvBase
{
    // Static values here
    private static int kCursorSize = 4;

    // Member vars here
    // Filename we're loading from / saving to
    private Levels.List m_name;
    // Texture for both drawing the mouse pointer and highlighting our current selection
    private Texture2D m_mouseTex;
    private EditorCamera m_camera;
    // Everything we're drawing to the screen
    private List<CollidableObject> m_objectList;
    // For getting mouse position and button status
    private UserMouse m_mouse;
    // Object currently being drawn at the mouse's position, for placement
    private CollidableObject m_mouseObject;
    // The object currently hovered over by the mouse, matching the type we're currently working with
    private CollidableObject m_currentSelection;
    // Y and Z positions to place an object. X is determined by mouse pointer
    private int m_currentZ;
    private int m_currentY;
    // All of the currently working types. Update this as more types work.
    private int m_currentType;
    private ObjectFactory.TypeEnum[] m_workingTypes =
    {
        ObjectFactory.TypeEnum.QuinHero,
        ObjectFactory.TypeEnum.Steve,
        ObjectFactory.TypeEnum.Professor,
        ObjectFactory.TypeEnum.Street,
        ObjectFactory.TypeEnum.Incubator,
        ObjectFactory.TypeEnum.Building1,
        ObjectFactory.TypeEnum.Building2,
        ObjectFactory.TypeEnum.LoadingZone,
        ObjectFactory.TypeEnum.Bench,
        ObjectFactory.TypeEnum.LargeDebris2,
        ObjectFactory.TypeEnum.LargeDebris3,
        ObjectFactory.TypeEnum.LargeDebris4,
        ObjectFactory.TypeEnum.Debris2,
        ObjectFactory.TypeEnum.Debris3,
        ObjectFactory.TypeEnum.Debris4,
        ObjectFactory.TypeEnum.Debris5,
        ObjectFactory.TypeEnum.Mailbox,
        ObjectFactory.TypeEnum.StreetLight,
        ObjectFactory.TypeEnum.TrashCan,
        ObjectFactory.TypeEnum.PowerPole,
        ObjectFactory.TypeEnum.PowerPoleNWires,
        ObjectFactory.TypeEnum.BrokenPowerPole,
        ObjectFactory.TypeEnum.OozeContainer,
        ObjectFactory.TypeEnum.Ooze,
        ObjectFactory.TypeEnum.Fire,
        ObjectFactory.TypeEnum.DownPower,
        ObjectFactory.TypeEnum.Television,
        ObjectFactory.TypeEnum.Bear,
        ObjectFactory.TypeEnum.Laptop,
        ObjectFactory.TypeEnum.Boulder,
        ObjectFactory.TypeEnum.Dumpster,
        ObjectFactory.TypeEnum.Fridge,
        ObjectFactory.TypeEnum.Truck,
        ObjectFactory.TypeEnum.BoulderTrigger,
//        ObjectFactory.TypeEnum.Nuke
//        ObjectFactory.TypeEnum.Hero
    };

    /**
     * Constructor.
     */
    public EditorEnv(Levels.List levelname)
    {        
        Game.getGame().getSoundManager().stopSound();
        m_mouse = new UserMouse();
        m_name = levelname;

        // Create an image for drawing the mouse's current location
        m_mouseTex = new Texture2D(Game.getGame().getGraphics().GraphicsDevice, kCursorSize, kCursorSize, 1, TextureUsage.None, SurfaceFormat.Color);
        Color[] texture = new Color[kCursorSize * kCursorSize];
        for (int i = 0; i < texture.Length; ++i)
        {
            texture[i] = Color.White;
        }
        m_mouseTex.SetData(texture);

        // Use as a position to snap the camera to when loading
        Vector3 camPos = Vector3.Zero;

        // Load a level
        List<SpawnPoint> spList = EnvBase.LoadSpawnList(m_name);
        m_objectList = new List<CollidableObject>();
        // Spawn each CO in the list
        foreach (SpawnPoint sp in spList)
        {
            m_objectList.Add((CollidableObject)sp.spawn());
            if (sp.getType() == ObjectFactory.TypeEnum.QuinHero)
            {
                // This is the player, use his position for our camera's starting point
                camPos = sp.getPosition();
            }
        }

        if (camPos != Vector3.Zero)
        {
            m_camera = new EditorCamera(new Vector3(camPos.X - Game.kScreenWidth/2, camPos.Y - Game.kScreenHeight, 0));
        }
        else
        {
            m_camera = new EditorCamera(new Vector3(0, 5000 - Game.kScreenHeight, 0));
        }

        m_currentZ = 0;
        m_currentY = 5000;

        m_currentType = 0;
        m_mouseObject = (CollidableObject)ObjectFactory.get(m_workingTypes[m_currentType], Vector3.Zero);
    }

    /**
     * Used internally to save our level. Converts our COs into SpawnPoints
     * that we can then save using EnvBase's SaveSpawnList.
     */
    private void SaveData()
    {
        List<SpawnPoint> spList = new List<SpawnPoint>();
        foreach (CollidableObject co in m_objectList)
        {
            spList.Add(new SpawnPoint(co.position, co.myType));
        }

        // Save out our spawn data
        EnvBase.SaveSpawnList(spList, m_name);
    }

    /**
     * Used internally to test whether our mouse cursor intersects with
     * a given VO.
     */
    private bool PointIsInside(Vector2 screenPos, CollidableObject obj)
    {
        if (ObjToRect(obj).Contains((int)screenPos.X, (int)screenPos.Y))
        {
            return true;
        }
        return false;
    }

    /**
     * Used internally to convert a VO into the appropriate Rectangle.
     */
    private Rectangle ObjToRect(CollidableObject obj)
    {
        Vector3 topLeft = Camera.worldToScreen(new Vector3(obj.collisionMin.X, obj.collisionMin.Y, obj.collisionMax.Z));
        Vector3 bottomRight = Camera.worldToScreen(new Vector3(obj.collisionMax.X, obj.collisionMax.Y, obj.collisionMin.Z));

        return new Rectangle((int)topLeft.X, (int)topLeft.Y,
            (int)Math.Abs(bottomRight.X - topLeft.X), (int)Math.Abs(topLeft.Y - bottomRight.Y));
    }

    /**
     * Update. Reads player's inputs and appropriately selects 
     * tiles to place, places those tiles, and saves out data.
     */
    public override void update(double dt)
    {
        InputManager im = InputManager.getSingleton();
        im.update();
        m_mouse.update();

        // Move camera as directed by user's inputs
        if(im.getControlState(InputManager.UP_BUTTON)) { m_camera.moveUp(); }
        else if(im.getControlState(InputManager.DOWN_BUTTON)) { m_camera.moveDown(); }
        if(im.getControlState(InputManager.LEFT_BUTTON)) { m_camera.moveLeft(); }
        else if(im.getControlState(InputManager.RIGHT_BUTTON)) { m_camera.moveRight(); }

        if(im.getJustPressedControlState(InputManager.BACK_BUTTON))
        {
            // Press back button to quit to menu
            m_parentStack.popAll(new MenuBase());
        }
        else if (im.getJustPressedControlState(InputManager.PAUSE_BUTTON))
        {
            // Press pause button to save and play our level
            SaveData();
            m_parentStack.popOnce(new LevelBase(m_name));
        }

        if (im.getJustPressedControlState(InputManager.DYNAMITE_BUTTON))
        {
            // "A" key

            // Cycle forward through workable types
            ++m_currentType;
            if (m_currentType >= m_workingTypes.Length)
            {
                m_currentType = 0;
            }
            // Spawn our new type at our mouse's location
            m_mouseObject = ObjectFactory.get(m_workingTypes[m_currentType],
                new Vector3(Camera.screenToWorld(m_mouse.getPosition()).X, m_currentY, m_currentZ));
        }
        else if (im.getJustPressedControlState(InputManager.JETPACK_BUTTON))
        {
            // "W" key

            // Cycle backward through workable types
            --m_currentType;
            if (m_currentType < 0)
            {
                m_currentType = m_workingTypes.Length - 1;
            }
            m_mouseObject = ObjectFactory.get(m_workingTypes[m_currentType],
                new Vector3(Camera.screenToWorld(m_mouse.getPosition()).X, m_currentY, m_currentZ));
        }
        // Manually check for keyboard input outside of normal control
        KeyboardState ks = Keyboard.GetState();
        // Move the Y or Z values up/down by increments of 1 or 50
             if (ks.IsKeyDown(Keys.G)) { m_currentY -= 50; }
        else if (ks.IsKeyDown(Keys.F)) { m_currentY -=  1; }
        else if (ks.IsKeyDown(Keys.D)) { m_currentY +=  1; }
        else if (ks.IsKeyDown(Keys.S)) { m_currentY += 50; }
        else if (ks.IsKeyDown(Keys.X)) { m_currentZ -= 50; }
        else if (ks.IsKeyDown(Keys.C)) { m_currentZ -=  1; }
        else if (ks.IsKeyDown(Keys.V)) { m_currentZ +=  1; }
        else if (ks.IsKeyDown(Keys.B)) { m_currentZ += 50; }

        // Snap Z-values to practical bounds
        if (m_currentZ < 0) { m_currentZ = 0; }
        else if (m_currentZ > 2000) { m_currentZ = 2000; }

        // Check mouse inputs
        if (m_mouse.leftPressed())
        {
            // Create a new copy of our working object and place into our object list
            CollidableObject newObj = ObjectFactory.get(m_workingTypes[m_currentType], new Vector3(Camera.screenToWorld(m_mouse.getPosition()).X, m_currentY, m_currentZ));
            m_objectList.Add(newObj);
        }
        if (m_mouse.rightPressed())
        {
            if (m_currentSelection != null)
            {
                // If we're currently highlighting an object of our working type, delete it
                m_objectList.Remove(m_currentSelection);
            }
        }

        // Null out our selected object each frame to re-check for a new selection
        m_currentSelection = null;
        foreach (CollidableObject co in m_objectList)
        {
            if(typeof(ViewableObject).IsAssignableFrom(co.GetType()))
            {
                // Update the positions of all ViewableObjects
                ((ViewableObject)co).editorUpdate();
            }

            if (PointIsInside(m_mouse.getPosition(), co) && co.myType.myType == m_workingTypes[m_currentType])
            {
                // This is the object we're currently hovering over
                m_currentSelection = co;
            }
        }
        if (m_currentSelection != null)
        {
            Game.getGame().getConsole().print("Selected: " + m_currentSelection.myType);
        }
        else
        {
            Game.getGame().getConsole().print("Selected: None");
        }

        // Update position of our current working object
        m_mouseObject.position = new Vector3(Camera.screenToWorld(m_mouse.getPosition()).X, m_currentY, m_currentZ);
        if (typeof(ViewableObject).IsAssignableFrom(m_mouseObject.GetType()))
        {
            // Working object should always be a ViewableObject, but checking just in case
            ((ViewableObject)m_mouseObject).editorUpdate();
        }

        Game.getGame().getConsole().print("ObjectPosition: " + 
            new Vector3(Camera.screenToWorld(m_mouse.getPosition()).X, m_currentY, m_currentZ).ToString() );
        Game.getGame().getConsole().print("MousePosition:  " + Camera.screenToWorld(m_mouse.getPosition()).ToString());
    }

    /**
     * Draws all of the spawnPoints currently on screen.
     */
    public override void render()
    {
        // Draw all our current objects

        foreach (CollidableObject co in m_objectList)
        {
            if(co == m_currentSelection)
            {
                // We're moused over this object and it's our working type, highlight it
                Game.getGame().getSpriteBatch().Draw(m_mouseTex, ObjToRect(co), null, new Color(255, 0, 0, 128), 
                    0.0f, Vector2.Zero, SpriteEffects.None, 0.0f);
            }
            co.draw();
        }
        // Draw object we're currently working with
        m_mouseObject.draw();
        // Draw mouse pointer
        Game.getGame().getSpriteBatch().Draw(m_mouseTex, m_mouse.getPosition(), null, Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
    }

    /**
     * Internal class to help us get mouse input more easily
     */
    private class UserMouse
    {
        // Member vars
        private bool m_prevLeftPressed;
        private bool m_prevRightPressed;
        private bool m_leftPressed;
        private bool m_rightPressed;
        private Vector2 m_position;

        public UserMouse()
        {
            m_prevLeftPressed = false;
            m_prevRightPressed = false;
            m_leftPressed = false;
            m_rightPressed = false;
            m_position = Vector2.Zero;
        }
        
        /**
         * Update our logics. Pass our current states to our prevStates, then update
         * our current information.
         */
        public void update()
        {
            m_prevLeftPressed = m_leftPressed;
            m_prevRightPressed = m_rightPressed;

            MouseState ms = Mouse.GetState();
            m_position = new Vector2(ms.X, ms.Y);

            m_leftPressed = ms.LeftButton == ButtonState.Pressed ? true : false;
            m_rightPressed = ms.RightButton == ButtonState.Pressed ? true : false;
        }

        /**
         * Get mouse button states. Pressed refers to just being pressed on that frame.
         * Released refers to just being released on that frame. Held refers to being held down.
         */
        public bool leftPressed()   { return m_leftPressed && !m_prevLeftPressed; }
        public bool rightPressed()  { return m_rightPressed && !m_prevRightPressed; }
        public bool leftHeld()      { return m_leftPressed; }
        public bool rightHeld()     { return m_rightPressed; }
        public bool leftReleased()  { return !m_leftPressed && m_prevLeftPressed; }
        public bool rightReleased() { return !m_rightPressed && m_prevRightPressed; }

        /**
         * Get our position as a Vec2.
         */
        public Vector2 getPosition() { return m_position; }
    }
}
